package com.aaa.sso.config;

import com.aaa.entity.LoginInfo;
import com.aaa.sso.feign.LoginInfoFeign;
import com.aaa.sso.feign.UserFeign;
import com.aaa.sso.utils.GetBrowser;
import com.aaa.sso.utils.GetOs;
import com.alibaba.fastjson.JSON;

import com.aaa.utils.JWTUtils;
import com.aaa.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 如果SpringBoot的版本过高，WebSecurityConfigurerAdapter就过时了
 * 前后端分离项目，不能有页面
 */
@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 密码加密器，自定义构建多个用户时，用户的密码必须使用密码加密器进行加密
     */
    @Bean
    public  PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private UserDetailsService userDetailsService;

    //注入userFeign为了能通过手机号获取当前登录用户的信息
    @Resource
    private UserFeign userFeign;

    //登录日志的feign是为了添加登陆日志信息
    @Resource
    private LoginInfoFeign loginInfoFeign;

    //redis的模板里面封装了很多方法，这里是为了将token存入到redis缓存中，
    //使用的String类型的序列化方式，在其它地方使用redis时，注入的也要是String类型的redis，否则会因为序列化方式的不同导致操作失败
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        /**
         * userDetailsService代表使用的是数据库
         * 传递一个userDetailsService对象，查询数据库完成相应的功能
         */
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //将自定义的过滤器添加到过滤器链中并添加到 UsernamePasswordAuthenticationFilter 过滤器前面
        // http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);

        //放行登录表单
        http.formLogin()
                .loginProcessingUrl("/login") //放行自己表单的登录处理路径[因为自己的表单提交路径为 /login]
                .successHandler(successHandler())  //成功处理类：参数类型为AuthenticationSuccessHandler
                .failureHandler(failureHandler())  //失败处理类：参数类型为AuthenticationFailureHandler
                .permitAll();

        //权限不足处理类:参数类型为AccessDeniedHandler
        http.exceptionHandling().accessDeniedHandler(handler());

        //禁用csrf的校验
        http.csrf().disable();

        //SpringSecurity允许跨域
        http.cors();

        //其他资源认证即可访问
        http.authorizeRequests().anyRequest().authenticated();
    }



    /**
     * 定义登陆成功处理类的参数，成功后可以返回json数据(这里使用的依赖是阿里的fastjson)
     * @return
     */
    private AuthenticationSuccessHandler successHandler(){
        return  new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                //1-1、定义响应json数据的编码格式
                response.setContentType("application/json;charset=utf-8");
                PrintWriter writer = response.getWriter();
                /**
                 * 用户的信息已经封装到authentication对象中
                 *其中用户的权限信息存储在Principal中，数据结构形式是User类型的，所以这里强制转换为User类型即可
                 */
                //1-2、获取当前登录用户的所有信息
                User user = (User) authentication.getPrincipal();
                //1-3、获取当前登录用户的用户名
                String username = user.getUsername();
                //1-4、获取当前的登录用户的权限,并将权限中的权限码放到一个集合中
                Collection<GrantedAuthority> authorities = user.getAuthorities();
                List<String> collect = authorities.stream().map(item -> item.getAuthority()).collect(Collectors.toList());
                //1-5、将用户名和用户权限封装到map对象中
                Map<String,Object> map = new HashMap<>();
                map.put("username",username);
                map.put("authorities",collect);
                //1-6、使用自己编写的token工具类生成token
                String token = JWTUtils.getToken(map);

                //token续期，每次访问后台的操作都会刷新token的生命周期，设置为30分钟
                redisTemplate.opsForValue().set(token,JSON.toJSONString(map),30, TimeUnit.MINUTES);

                /**
                 * 登录成功日志
                 */
                //1、登录用户的姓名
                com.aaa.entity.User user1 = userFeign.getByUsername(username);
                String user_name = user1.getUserName();
                //2、登录用户的手机号
                String login_account = user1.getPhone();
                //3、登录的ip
                String ip_addr = request.getRemoteAddr();
                //4、登录类型 内网还是外网
                String login_location = ip_addr.startsWith("192.168.")?"内网IP":"外网IP";
                //5、浏览器
                String Agent = request.getHeader("User-Agent");
                String browser = GetBrowser.getBrowser(Agent);
                //6、操作系统
                String os = GetOs.getOs(Agent);
                //7、登录状态 成功(0)、失败(1)
                String login_status = "0";
                //8、登录类型 系统用户（0）、患者用户（1）
                String login_type = "0";
                //9、登陆成功信息
                String msg = "登陆成功";
                //10、登录时间
                LocalDateTime login_time = LocalDateTime.now();
                LoginInfo loginInfo = new LoginInfo(null,user_name,login_account,ip_addr,login_location,browser,os,login_status,login_type,msg,login_time);
                loginInfoFeign.addLoginInfo(loginInfo);

                //1-7、响应一个公共实体类
                Result<String> result = new Result(200,"登录成功",token);
                //1-8、将生成的实体类对象转换成json数据
                String jsonString = JSON.toJSONString(result);
                //1-9、将生成的json数据响应给前端
                writer.print(jsonString);
                //1-10、关闭数据流
                writer.flush();
                writer.close();

            }
        };
    }

    /**
     * 定义登陆失败处理类的参数，失败后返回异常即可
     * @return
     */
    private AuthenticationFailureHandler failureHandler(){
        return new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                //1、定义响应json数据的编码格式
                response.setContentType("application/json;charset=utf-8");
                PrintWriter writer = response.getWriter();

                /**
                 * 登录失败日志
                 */
                //1、登录用户的姓名
                String username = request.getParameter("username"); //这里是获取的手机号
                com.aaa.entity.User user1 = userFeign.getByUsername(username);
                String user_name = "";
                //如果当前手机号不存在，那么就将用户名赋值为空
                if (Objects.nonNull(user1)){
                     user_name = user1.getUserName();
                }else{
                     user_name = "";
                }
                //2、登录用户的手机号
                String login_account = username;
                //3、登录的ip
                String ip_addr = request.getRemoteAddr();
                //4、登录类型 内网还是外网
                String login_location = ip_addr.startsWith("192.168.")?"内网IP":"外网IP";
                //5、浏览器
                String Agent = request.getHeader("User-Agent");
                String browser = GetBrowser.getBrowser(Agent);
                //6、操作系统
                String os = GetOs.getOs(Agent);
                //7、登录状态 成功(0)、失败(1)
                String login_status = "1";
                //8、登录类型 系统用户（0）、患者用户（1）
                String login_type = "0";
                //9、登陆成功信息
                String msg = "登陆失败";
                //10、登录时间
                LocalDateTime login_time = LocalDateTime.now();
                LoginInfo loginInfo = new LoginInfo(null,user_name,login_account,ip_addr,login_location,browser,os,login_status,login_type,msg,login_time);
                loginInfoFeign.addLoginInfo(loginInfo);

                //1-2、响应一个公共实体类
                Result<String> result = new Result(500,e.getMessage());
                //1-3、将生成的实体类对象转换成json数据
                String jsonString = JSON.toJSONString(result);
                //1-4、将生成的json数据响应给前端
                writer.print(jsonString);
                //1-5、关闭数据流
                writer.flush();
                writer.close();
            }
        };
    }

    /**
     * 定义权限不足处理类的参数，权限不足时返回异常
     * @return
     */
    private AccessDeniedHandler handler(){
        return new AccessDeniedHandler() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
                //1、定义响应json数据的编码格式
                response.setContentType("application/json;charset=utf-8");
                PrintWriter writer = response.getWriter();
                //7、响应一个公共实体类
                Result<String> result = new Result(403,e.getMessage());
                //8、将生成的实体类对象转换成json数据
                String jsonString = JSON.toJSONString(result);
                //9、将生成的json数据响应给前端
                writer.print(jsonString);
                //10、关闭数据流
                writer.flush();
                writer.close();
            }
        };
    }
}
